home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / FILESTORAGE.PY < prev    next >
Encoding:
Python Source  |  2000-09-07  |  59.1 KB  |  1,738 lines

  1. ##############################################################################
  2. # Zope Public License (ZPL) Version 1.0
  3. # -------------------------------------
  4. # Copyright (c) Digital Creations.  All rights reserved.
  5. # This license has been certified as Open Source(tm).
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions are
  8. # met:
  9. # 1. Redistributions in source code must retain the above copyright
  10. #    notice, this list of conditions, and the following disclaimer.
  11. # 2. Redistributions in binary form must reproduce the above copyright
  12. #    notice, this list of conditions, and the following disclaimer in
  13. #    the documentation and/or other materials provided with the
  14. #    distribution.
  15. # 3. Digital Creations requests that attribution be given to Zope
  16. #    in any manner possible. Zope includes a "Powered by Zope"
  17. #    button that is installed by default. While it is not a license
  18. #    violation to remove this button, it is requested that the
  19. #    attribution remain. A significant investment has been put
  20. #    into Zope, and this effort will continue if the Zope community
  21. #    continues to grow. This is one way to assure that growth.
  22. # 4. All advertising materials and documentation mentioning
  23. #    features derived from or use of this software must display
  24. #    the following acknowledgement:
  25. #      "This product includes software developed by Digital Creations
  26. #      for use in the Z Object Publishing Environment
  27. #      (http://www.zope.org/)."
  28. #    In the event that the product being advertised includes an
  29. #    intact Zope distribution (with copyright and license included)
  30. #    then this clause is waived.
  31. # 5. Names associated with Zope or Digital Creations must not be used to
  32. #    endorse or promote products derived from this software without
  33. #    prior written permission from Digital Creations.
  34. # 6. Modified redistributions of any form whatsoever must retain
  35. #    the following acknowledgment:
  36. #      "This product includes software developed by Digital Creations
  37. #      for use in the Z Object Publishing Environment
  38. #      (http://www.zope.org/)."
  39. #    Intact (re-)distributions of any official Zope release do not
  40. #    require an external acknowledgement.
  41. # 7. Modifications are encouraged but must be packaged separately as
  42. #    patches to official Zope releases.  Distributions that do not
  43. #    clearly separate the patches from the original work must be clearly
  44. #    labeled as unofficial distributions.  Modifications which do not
  45. #    carry the name Zope may be packaged in any form, as long as they
  46. #    conform to all of the clauses above.
  47. # Disclaimer
  48. #   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
  49. #   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  50. #   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  51. #   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
  52. #   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  53. #   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  54. #   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  55. #   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  56. #   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  57. #   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  58. #   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  59. #   SUCH DAMAGE.
  60. # This software consists of contributions made by Digital Creations and
  61. # many individuals on behalf of Digital Creations.  Specific
  62. # attributions are listed in the accompanying credits file.
  63. ##############################################################################
  64. #  File-based ZODB storage
  65. # Files are arranged as follows.
  66. #   - The first 4 bytes are a file identifier.
  67. #   
  68. #   - The rest of the file consists of a sequence of transaction
  69. #     "records".
  70. # A transaction record consists of:
  71. #   - 8-byte transaction id, which is also a time stamp.
  72. #   
  73. #   - 8-byte transaction record length - 8.
  74. #   
  75. #   - 1-byte status code
  76. #   
  77. #   - 2-byte length of user name
  78. #   
  79. #   - 2-byte length of description 
  80. #   
  81. #   - 2-byte length of extension attributes 
  82. #   
  83. #   -   user name
  84. #   
  85. #   -   description
  86. #   * A sequence of data records
  87. #   
  88. #   - 8-byte redundant transaction length -8
  89. # A data record consists of
  90. #   - 8-byte oid.
  91. #   - 8-byte serial, which is a type stamp that matches the
  92. #     transaction timestamp.
  93. #   - 8-byte previous-record file-position.
  94. #   - 8-byte beginning of transaction record file position.
  95. #   - 2-byte version length
  96. #   - 8-byte data length
  97. #   ? 8-byte position of non-version data
  98. #     (if version length > 0)
  99. #   ? 8-byte position of previous record in this version
  100. #     (if version length > 0)
  101. #   ?   version string 
  102. #     (if version length > 0)
  103. #   ?   data
  104. #     (data length > 0)
  105. #   ? 8-byte position of data record containing data
  106. #     (data length == 0)
  107. # Note that the lengths and positions are all big-endian.
  108. # Also, the object ids time stamps are big-endian, so comparisons
  109. # are meaningful.
  110. # Version handling
  111. #   There isn't a separate store for versions.  Each record has a
  112. #   version field, indicating what version it is in.  The records in a
  113. #   version form a linked list.  Each record that has a non-empty
  114. #   version string has a pointer to the previous record in the version.
  115. #   Version back pointers are retained *even* when versions are
  116. #   committed or aborted or when transactions are undone.
  117. #   There is a notion of "current" version records, which are the
  118. #   records in a version that are the current records for their
  119. #   respective objects.  When a version is comitted, the current records
  120. #   are committed to the destination version.  When a version is
  121. #   aborted, the current records are aborted.
  122. #   When committing or aborting, we search backward through the linked
  123. #   list until we find a record for an object that does not have a
  124. #   current record in the version.  If we find a record for which the
  125. #   non-version pointer is the same as the previous pointer, then we
  126. #   forget that the corresponding object had a current record in the
  127. #   version. This strategy allows us to avoid searching backward through
  128. #   previously committed or aborted version records.
  129. #   Of course, we ignore records in undone transactions when committing
  130. #   or aborting.
  131. #
  132. # Backpointers
  133. #
  134. #   When we commit or abort a version, we don't copy (or delete)
  135. #   and data.  Instead, we write records with back pointers.
  136. #
  137. #   A version record *never* has a back pointer to a non-version
  138. #   record, because we never abort to a version.  A non-version record
  139. #   may have a back pointer to a version record or to a non-version
  140. #   record.
  141. #
  142. __version__='$Revision: 1.37.12.8 $'[11:-2]
  143.  
  144. import struct, time, os, bpthread, string, base64, sys
  145. from struct import pack, unpack
  146. from cPickle import loads
  147. import POSException
  148. from TimeStamp import TimeStamp
  149. from lock_file import lock_file
  150. from utils import t32, p64, U64, cp
  151. from zLOG import LOG, WARNING, ERROR, PANIC, register_subsystem
  152. register_subsystem('ZODB FS')
  153. import BaseStorage
  154. from cPickle import Pickler, Unpickler
  155.  
  156. try: from posix import fsync
  157. except: fsync=None
  158.  
  159. z64='\0'*8
  160.  
  161. def warn(message, *data):
  162.     LOG('ZODB FS',WARNING, "%s  warn: %s\n" % (packed_version, (message % data)))
  163.  
  164. def error(message, *data):
  165.     LOG('ZODB FS',ERROR,"%s ERROR: %s\n" % (packed_version, (message % data)))
  166.  
  167. def nearPanic(message, *data):
  168.     LOG('ZODB FS',PANIC,"%s ERROR: %s\n" % (packed_version, (message % data)))
  169.  
  170. def panic(message, *data):
  171.     message=message%data
  172.     LOG('ZODB FS',PANIC,"%s ERROR: %s\n" % (packed_version, message))
  173.     raise CorruptedTransactionError, message
  174.  
  175. class FileStorageError(POSException.StorageError): pass
  176.  
  177. class FileStorageFormatError(FileStorageError):
  178.     """Invalid file format
  179.  
  180.     The format of the given file is not valid
  181.     """
  182.  
  183. class CorruptedFileStorageError(FileStorageError,
  184.                                 POSException.StorageSystemError):
  185.     """Corrupted file storage
  186.     """
  187.  
  188. class CorruptedTransactionError(CorruptedFileStorageError): pass
  189. class CorruptedDataError(CorruptedFileStorageError): pass
  190.  
  191. class FileStorageQuotaError(FileStorageError,
  192.                                 POSException.StorageSystemError):
  193.     """File storage quota exceeded
  194.     """
  195.  
  196. packed_version='FS21'
  197.  
  198. class FileStorage(BaseStorage.BaseStorage):
  199.     _packt=z64
  200.  
  201.     def __init__(self, file_name, create=0, read_only=0, stop=None,
  202.                  quota=None):
  203.  
  204.         if not os.path.exists(file_name): create = 1
  205.  
  206.         if read_only:
  207.             if create: raise ValueError, "can\'t create a read-only file"
  208.         elif stop is not None:
  209.             raise ValueError, "time-travel is only supported in read-only mode"
  210.  
  211.         if stop is None: stop='\377'*8
  212.  
  213.         # Lock the database and set up the temp file.
  214.         if not read_only:
  215.             try: f=open(file_name+'.lock', 'r+')
  216.             except: f=open(file_name+'.lock', 'w+')
  217.             lock_file(f)
  218.             try:
  219.                 f.write(str(os.getpid()))
  220.                 f.flush()
  221.             except: pass
  222.             self._lock_file=f # so it stays open
  223.  
  224.             self._tfile=open(file_name+'.tmp','w+b')
  225.  
  226.         else:
  227.  
  228.             self._tfile=None
  229.  
  230.         BaseStorage.BaseStorage.__init__(self, file_name)
  231.  
  232.         index, vindex, tindex, tvindex = self._newIndexes()
  233.  
  234.         self._initIndex(index, vindex, tindex, tvindex)
  235.         
  236.         # Now open the file
  237.         
  238.         if create:
  239.             if os.path.exists(file_name): os.remove(file_name)
  240.             file=open(file_name,'w+b')
  241.             file.write(packed_version)
  242.         else:
  243.             file=open(file_name, read_only and 'rb' or 'r+b')
  244.  
  245.         self._file=file
  246.  
  247.         r=self._restore_index()
  248.         if r:
  249.             index, vindex, start, maxoid, ltid = r
  250.             self._initIndex(index, vindex, tindex, tvindex)
  251.             self._pos, self._oid, tid = read_index(
  252.                 file, file_name, index, vindex, tindex, stop,
  253.                 ltid=ltid, start=start, maxoid=maxoid,
  254.                 read_only=read_only,
  255.                 )
  256.         else:
  257.             self._pos, self._oid, tid = read_index(
  258.                 file, file_name, index, vindex, tindex, stop,
  259.                 read_only=read_only,
  260.                 )
  261.  
  262.         self._ts=tid=TimeStamp(tid)
  263.         t=time.time()
  264.         t=apply(TimeStamp,(time.gmtime(t)[:5]+(t%60,)))
  265.         if tid > t:
  266.             warn("%s Database records in the future", file_name);
  267.             if tid.timeTime() - t.timeTime() > 86400*30:
  268.                 # a month in the future? This is bogus, use current time
  269.                 self._ts=t
  270.  
  271.         self._quota=quota
  272.             
  273.  
  274.     def _initIndex(self, index, vindex, tindex, tvindex):
  275.         self._index=index
  276.         self._vindex=vindex
  277.         self._tindex=tindex
  278.         self._tvindex=tvindex
  279.         self._index_get=index.get
  280.         self._vindex_get=vindex.get
  281.         self._tappend=tindex.append
  282.  
  283.  
  284.     def __len__(self): return len(self._index)
  285.  
  286.     def _newIndexes(self): return {}, {}, [], {}
  287.         
  288.     def abortVersion(self, src, transaction):
  289.         return self.commitVersion(src, '', transaction, abort=1)
  290.  
  291.     def _save_index(self):
  292.         """Write the database index to a file to support quick startup
  293.         """
  294.         
  295.         index_name=self.__name__+'.index'
  296.         tmp_name=index_name+'.index_tmp'
  297.  
  298.         f=open(tmp_name,'wb')
  299.         p=Pickler(f,1)
  300.  
  301.         info={'index': self._index, 'pos': self._pos,
  302.               'oid': self._oid, 'vindex': self._vindex}
  303.  
  304.         p.dump(info)
  305.         f.flush()
  306.         f.close()
  307.         try:
  308.             try: os.unlink(index_name)
  309.             except: pass
  310.             os.rename(tmp_name, index_name)
  311.         except: pass
  312.  
  313.     def _clear_index(self):
  314.         index_name=self.__name__+'.index'
  315.         if os.path.exists(index_name):
  316.             os.unlink(index_name)
  317.  
  318.     def _sane(self, index, pos):
  319.         """Sanity check saved index data by reading the last undone trans
  320.  
  321.         Basically, we read the last not undone transaction and
  322.         check to see that the included records are consistent
  323.         with the index.  Any invalid record records or inconsistent
  324.         object positions cause zero to be returned.
  325.  
  326.         """
  327.         if pos < 100: return 0
  328.         file=self._file
  329.         seek=file.seek
  330.         read=file.read
  331.         seek(0,2)
  332.         if file.tell() < pos: return 0
  333.         ltid=None
  334.  
  335.         while 1:
  336.             seek(pos-8)
  337.             rstl=read(8)
  338.             tl=U64(rstl)
  339.             pos=pos-tl-8
  340.             if pos < 4: return 0
  341.             seek(pos)
  342.             tid, stl, status, ul, dl, el = unpack(">8s8scHHH", read(23))
  343.             if not ltid: ltid=tid
  344.             if stl != rstl: return 0 # inconsistent lengths
  345.             if status == 'u': continue # undone trans, search back
  346.             if status not in ' p': return 0
  347.             if ul > tl or dl > tl or el > tl or tl < (23+ul+dl+el): return 0
  348.             tend=pos+tl
  349.             opos=pos+(23+ul+dl+el)
  350.             if opos==tend: continue # empty trans
  351.  
  352.             while opos < tend:
  353.                 # Read the data records for this transaction    
  354.                 seek(opos)
  355.                 h=read(42)
  356.                 oid,serial,sprev,stloc,vlen,splen = unpack(">8s8s8s8sH8s", h)
  357.                 tloc=U64(stloc)
  358.                 plen=U64(splen)
  359.                 
  360.                 dlen=42+(plen or 8)
  361.                 if vlen: dlen=dlen+(16+vlen)
  362.     
  363.                 if opos+dlen > tend or tloc != pos: return 0
  364.  
  365.                 if index.get(oid,0) != opos: return 0
  366.     
  367.                 opos=opos+dlen
  368.  
  369.             return ltid
  370.  
  371.     def _restore_index(self):
  372.         """Load the database index from a file to support quick startup
  373.         """
  374.         file_name=self.__name__
  375.         index_name=file_name+'.index'
  376.         
  377.         try: f=open(index_name,'rb')
  378.         except: return None
  379.         
  380.         p=Unpickler(f)
  381.  
  382.         info=p.load()
  383.         index=info.get('index', None)
  384.         pos=long(info.get('pos', None))
  385.         oid=info.get('oid', None)
  386.         vindex=info.get('vindex', None)
  387.         if index is None or pos is None or oid is None or vindex is None:
  388.             return None
  389.  
  390.         tid=self._sane(index, pos)
  391.         if not tid: return None
  392.         
  393.         return index, vindex, pos, oid, tid
  394.  
  395.     def close(self):
  396.         self._file.close()
  397.         self._lock_file.close()
  398.         self._tfile.close()
  399.         try: self._save_index()
  400.         except: pass # We don't care if this fails.
  401.         
  402.     def commitVersion(self, src, dest, transaction, abort=None):
  403.         # We are going to commit by simply storing back pointers.
  404.  
  405.         if dest and abort:
  406.             raise 'VersionCommitError', (
  407.                 'Internal error, can\'t abort to a version')
  408.         
  409.         if transaction is not self._transaction:
  410.             raise POSException.StorageTransactionError(self, transaction)
  411.         
  412.         self._lock_acquire()
  413.         try:
  414.             file=self._file
  415.             read=file.read
  416.             seek=file.seek
  417.             tfile=self._tfile
  418.             write=tfile.write
  419.             tappend=self._tappend
  420.             index=self._index
  421.             index_get=index.get
  422.  
  423.             srcpos=self._vindex_get(src, 0)
  424.             spos=p64(srcpos)
  425.             middle=struct.pack(">8sH8s", p64(self._pos), len(dest), z64)
  426.  
  427.             if dest:
  428.                 sd=p64(self._vindex_get(dest, 0))
  429.                 heredelta=66+len(dest)
  430.             else:
  431.                 sd=''
  432.                 heredelta=50
  433.                         
  434.             here=self._pos+(tfile.tell()+self._thl)
  435.             oids=[]
  436.             appoids=oids.append
  437.             tvindex=self._tvindex
  438.             current_oids={}
  439.             current=current_oids.has_key
  440.             t=None
  441.             tstatus=' '
  442.  
  443.             while srcpos:
  444.                 seek(srcpos)
  445.                 h=read(58) # oid, serial, prev(oid), tloc, vlen, plen, pnv, pv
  446.                 oid=h[:8]
  447.                 pnv=h[-16:-8]
  448.                 if index_get(oid, None) == srcpos:
  449.                     # This is a current record!
  450.                     tappend((oid,here))
  451.                     appoids(oid)
  452.                     write(h[:16] + spos + middle)
  453.                     if dest:
  454.                         tvindex[dest]=here
  455.                         write(pnv+sd+dest)
  456.                         sd=p64(here)
  457.  
  458.                     write(abort and pnv or spos) # data backpointer to src data
  459.                     here=here+heredelta
  460.  
  461.                     if h[16:24] != pnv:
  462.                         # This is not the first current record, so mark it
  463.                         current_oids[oid]=1
  464.  
  465.                 else:
  466.                     # Hm.  This is a non-current record.  Is there a
  467.                     # current record for this oid?
  468.                     if not current(oid):
  469.                         # Nope. We're done *if* this transaction wasn't undone.
  470.                         tloc=h[24:32]
  471.                         if t != tloc:
  472.                             # We haven't checked this transaction before,
  473.                             # get it's status.
  474.                             t=tloc
  475.                             seek(U64(t)+16)
  476.                             tstatus=read(1)
  477.                             
  478.                         if tstatus != 'u':
  479.                             # Yee ha! We can quit
  480.                             break
  481.  
  482.                     # The following optimization fails miserably
  483.                     # if, for some reason, an object is written twice
  484.                     # in the same transaction!
  485.                     #elif h[16:24] == pnv and pnv != z64:
  486.                     #    # This is the first current record, so unmark it.
  487.                     #    # Note that we don't need to check if this was
  488.                     #    # undone.  If it *was* undone, then there must
  489.                     #    # be a later record that is the first record, or
  490.                     #    # there isn't a current record.  In either case,
  491.                     #    # we can't be in this branch. :)
  492.                     #    del current_oids[oid]
  493.                     
  494.                 spos=h[-8:]
  495.                 srcpos=U64(spos)
  496.  
  497.             return oids
  498.  
  499.         finally: self._lock_release()
  500.  
  501.     def getSize(self): return self._pos
  502.  
  503.     def _loada(self, oid, _index, file):
  504.         "Read any version and return the version"
  505.         pos=_index[oid]
  506.         file.seek(pos)
  507.         read=file.read
  508.         h=read(42)
  509.         doid,serial,prev,tloc,vlen,plen = unpack(">8s8s8s8sH8s", h)
  510.         if vlen:
  511.             nv = read(8) != z64
  512.             file.seek(8,1) # Skip previous version record pointer
  513.             version=read(vlen)
  514.         else:
  515.             version=''
  516.             nv=0
  517.  
  518.         if plen != z64: return read(U64(plen)), version, nv
  519.         return _loadBack(file, oid, read(8))[0], version, nv
  520.  
  521.     def _load(self, oid, version, _index, file):
  522.         pos=_index[oid]
  523.         file.seek(pos)
  524.         read=file.read
  525.         h=read(42)
  526.         doid,serial,prev,tloc,vlen,plen = unpack(">8s8s8s8sH8s", h)
  527.         if doid != oid: raise CorruptedDataError, h
  528.         if vlen:
  529.             pnv=read(8) # Read location of non-version data
  530.             if (not version or len(version) != vlen or
  531.                 (read(8) # skip past version link
  532.                  and version != read(vlen))
  533.                 ):
  534.                 return _loadBack(file, oid, pnv)
  535.  
  536.         # If we get here, then either this was not a version record,
  537.         # or we've already read past the version data!
  538.         if plen != z64: return read(U64(plen)), serial
  539.         pnv=read(8)
  540.         # We use the current serial, since that is the one that
  541.         # will get checked when we store.
  542.         return _loadBack(file, oid, pnv)[0], serial
  543.  
  544.     def load(self, oid, version, _stuff=None):
  545.         self._lock_acquire()
  546.         try: return self._load(oid, version, self._index, self._file)
  547.         finally: self._lock_release()
  548.  
  549.     def loadSerial(self, oid, serial):
  550.         self._lock_acquire()
  551.         try:
  552.             _index=self._index
  553.             file=self._file
  554.             seek=file.seek
  555.             read=file.read
  556.             pos=_index[oid]
  557.             while 1:
  558.                 seek(pos)
  559.                 h=read(42)
  560.                 doid,dserial,prev,tloc,vlen,plen = unpack(">8s8s8s8sH8s", h)
  561.                 if doid != oid: raise CorruptedDataError, h
  562.                 if dserial == serial: break # Yeee ha!
  563.                 # Keep looking for serial
  564.                 pos=U64(prev)
  565.                 if not pos: raise KeyError, serial
  566.                 continue
  567.  
  568.             if vlen:
  569.                 pnv=read(8) # Read location of non-version data
  570.                 read(8) # skip past version link
  571.                 read(vlen) # skip version
  572.  
  573.             if plen != z64: return read(U64(plen))
  574.  
  575.             # We got a backpointer, probably from a commit.
  576.             pnv=read(8)
  577.             return _loadBack(file, oid, pnv)[0]
  578.         finally: self._lock_release()
  579.                     
  580.     def modifiedInVersion(self, oid):
  581.         self._lock_acquire()
  582.         try:
  583.             pos=self._index[oid]
  584.             file=self._file
  585.             seek=file.seek
  586.             seek(pos)
  587.             doid,serial,prev,tloc,vlen = unpack(">8s8s8s8sH", file.read(34))
  588.             if doid != oid:
  589.                 raise CorruptedDataError, pos
  590.             if vlen:
  591.                 seek(24,1) # skip plen, pnv, and pv
  592.                 return file.read(vlen)
  593.             return ''
  594.         finally: self._lock_release()
  595.  
  596.     def store(self, oid, serial, data, version, transaction):
  597.         if transaction is not self._transaction:
  598.             raise POSException.StorageTransactionError(self, transaction)
  599.  
  600.         self._lock_acquire()
  601.         try:
  602.             old=self._index_get(oid, 0)
  603.             pnv=None
  604.             if old:
  605.                 file=self._file
  606.                 file.seek(old)
  607.                 read=file.read
  608.                 h=read(42)
  609.                 doid,oserial,sprev,stloc,vlen,splen = unpack(">8s8s8s8sH8s", h)
  610.                 if doid != oid: raise CorruptedDataError, h
  611.                 if vlen:
  612.                     pnv=read(8) # non-version data pointer
  613.                     read(8) # skip past version link
  614.                     locked_version=read(vlen)
  615.                     if version != locked_version:
  616.                         raise POSException.VersionLockError, (
  617.                             `oid`, locked_version)
  618.  
  619.                 if serial != oserial: raise POSException.ConflictError, (
  620.                     serial, oserial)
  621.  
  622.             tfile=self._tfile
  623.             write=tfile.write
  624.             pos=self._pos
  625.             here=pos+(tfile.tell()+self._thl)
  626.             self._tappend((oid, here))
  627.             serial=self._serial
  628.             write(pack(">8s8s8s8sH8s",
  629.                        oid,serial,p64(old),p64(pos),
  630.                        len(version),p64(len(data))
  631.                        )
  632.                   )
  633.             if version:
  634.                 if pnv: write(pnv)
  635.                 else:   write(p64(old))
  636.                 # Link to last record for this version:
  637.                 tvindex=self._tvindex
  638.                 pv=tvindex.get(version, 0) or self._vindex_get(version, 0)
  639.                 write(p64(pv))
  640.                 tvindex[version]=here
  641.                 write(version)
  642.  
  643.             write(data)
  644.  
  645.             # Check quota
  646.             quota=self._quota
  647.             if quota is not None and pos+(tfile.tell()+self._thl) > quota:
  648.                 raise FileStorageQuotaError, (
  649.                     'The storage quota has been exceeded.')
  650.  
  651.             return serial
  652.         
  653.         finally: self._lock_release()
  654.  
  655.     def supportsUndo(self): return 1
  656.     def supportsVersions(self): return 1
  657.  
  658.     def _clear_temp(self):
  659.         del self._tindex[:]
  660.         self._tvindex.clear()
  661.         self._tfile.seek(0)
  662.  
  663.     def _begin(self, tid, u, d, e):
  664.         self._thl=23+len(u)+len(d)+len(e)
  665.         self._nextpos=0
  666.  
  667.     def tpc_vote(self, transaction):
  668.         if transaction is not self._transaction:
  669.             raise POSException.StorageTransactionError(self, transaction)
  670.  
  671.         self._lock_acquire()
  672.         try:
  673.             tfile=self._tfile
  674.             dlen=tfile.tell()
  675.             if not dlen: return # No data in this trans
  676.             file=self._file
  677.             write=file.write
  678.             tfile.seek(0)
  679.             id=self._serial
  680.             user, desc, ext = self._ude
  681.             luser=len(user)
  682.             ldesc=len(desc)
  683.             lext=len(ext)
  684.  
  685.             # We have to check lengths here because struct.pack
  686.             # doesn't raise an exception on overflow!
  687.             if luser > 65535: raise FileStorageError, 'user name too long'
  688.             if ldesc > 65535: raise FileStorageError, 'description too long'
  689.             if lext  > 65535: raise FileStorageError, 'too much extension data'
  690.  
  691.             tlen=self._thl
  692.             pos=self._pos
  693.             file.seek(pos)
  694.             tl=tlen+dlen
  695.             stl=p64(tl)
  696.  
  697.             try:
  698.                 # Note that we use a status of 'c', for checkpoint.
  699.                 # If this flag isn't cleared, anything after this is
  700.                 # suspect.
  701.                 write(pack(
  702.                     ">8s" "8s" "c"  "H"        "H"        "H"
  703.                      ,id, stl, 'c', luser,     ldesc,     lext,
  704.                     ))
  705.                 if user: write(user)
  706.                 if desc: write(desc)
  707.                 if ext: write(ext)
  708.  
  709.                 cp(tfile, file, dlen)
  710.  
  711.                 write(stl)
  712.                 file.flush()
  713.             except:
  714.                 # Hm, an error occured writing out the data. Maybe the
  715.                 # disk is full. We don't want any turd at the end.
  716.                 file.truncate(pos)
  717.                 raise
  718.             
  719.             self._nextpos=pos+(tl+8)
  720.             
  721.         finally: self._lock_release()
  722.  
  723.     def _finish(self, tid, u, d, e):
  724.         nextpos=self._nextpos
  725.         if nextpos:
  726.             file=self._file
  727.  
  728.             # Clear the checkpoint flag
  729.             file.seek(self._pos+16)
  730.             file.write(' ')        
  731.             file.flush()
  732.  
  733.             if fsync is not None: fsync(file.fileno())
  734.  
  735.             self._pos=nextpos
  736.  
  737.             index=self._index
  738.             for oid, pos in self._tindex: index[oid]=pos
  739.  
  740.             self._vindex.update(self._tvindex)
  741.  
  742.     def _abort(self):
  743.         if self._nextpos: self._file.truncate(self._nextpos)
  744.  
  745.     def undo(self, transaction_id):
  746.         self._lock_acquire()
  747.         try:
  748.             self._clear_index()
  749.             transaction_id=base64.decodestring(transaction_id+'==\n')
  750.             tid, tpos = transaction_id[:8], U64(transaction_id[8:])
  751.             packt=self._packt
  752.             if packt is None or packt > tid:
  753.                 raise POSException.UndoError, (
  754.                     'Undo is currently disabled for database maintenance.<p>')
  755.  
  756.             file=self._file
  757.             seek=file.seek
  758.             read=file.read
  759.             index_get=self._index_get
  760.             unpack=struct.unpack
  761.             seek(tpos)
  762.             h=read(23)
  763.             if len(h) != 23 or h[:8] != tid: 
  764.                 raise POSException.UndoError, 'Invalid undo transaction id'
  765.             if h[16] == 'u': return
  766.             if h[16] != ' ':
  767.                 raise POSException.UndoError, 'Undoable transaction'
  768.             tl=U64(h[8:16])
  769.             ul,dl,el=unpack(">HHH", h[17:23])
  770.             tend=tpos+tl
  771.             pos=tpos+(23+ul+dl+el)
  772.             t={}
  773.             while pos < tend:
  774.                 # Read the data records for this transaction
  775.                 seek(pos)
  776.                 h=read(42)
  777.                 oid,serial,sprev,stloc,vlen,splen = unpack(">8s8s8s8sH8s", h)
  778.                 plen=U64(splen)
  779.                 prev=U64(sprev)
  780.                 dlen=42+(plen or 8)
  781.                 if vlen: dlen=dlen+(16+vlen)
  782.                 if index_get(oid,0) != pos:
  783.                     raise POSException.UndoError, 'Undoable transaction'
  784.                 pos=pos+dlen
  785.                 if pos > tend:
  786.                     raise POSException.UndoError, 'Undoable transaction'
  787.                 t[oid]=prev
  788.  
  789.             seek(tpos+16)
  790.             file.write('u')
  791.             file.flush()
  792.             index=self._index
  793.             for oid, pos in t.items(): index[oid]=pos
  794.             return t.keys()            
  795.         finally: self._lock_release()
  796.  
  797.     def undoLog(self, first, last, filter=None):
  798.         self._lock_acquire()
  799.         try:
  800.             packt=self._packt
  801.             if packt is None:
  802.                 raise POSException.UndoError, (
  803.                     'Undo is currently disabled for database maintenance.<p>')
  804.             pos=self._pos
  805.             if pos < 39: return []
  806.             file=self._file
  807.             seek=file.seek
  808.             read=file.read
  809.             unpack=struct.unpack
  810.             strip=string.strip
  811.             encode=base64.encodestring
  812.             r=[]
  813.             append=r.append
  814.             i=0
  815.             while i < last and pos > 39:
  816.                 seek(pos-8)
  817.                 pos=pos-U64(read(8))-8
  818.                 seek(pos)
  819.                 h=read(23)
  820.                 tid, tl, status, ul, dl, el = unpack(">8s8scHHH", h)
  821.                 if tid < packt: break
  822.                 if status != ' ': continue
  823.                 u=ul and read(ul) or ''
  824.                 d=dl and read(dl) or ''
  825.                 d={'id': encode(tid+p64(pos))[:22],
  826.                    'time': TimeStamp(tid).timeTime(),
  827.                    'user_name': u, 'description': d}
  828.                 if el:
  829.                     try: 
  830.                         e=loads(read(el))
  831.                         d.update(e)
  832.                     except: pass
  833.                 if filter is None or filter(d):
  834.                     if i >= first: append(d)
  835.                     i=i+1
  836.                 
  837.             return r
  838.         finally: self._lock_release()
  839.  
  840.     def versionEmpty(self, version):
  841.         self._lock_acquire()
  842.         try:
  843.             index=self._index
  844.             file=self._file
  845.             seek=file.seek
  846.             read=file.read
  847.             srcpos=self._vindex_get(version, 0)
  848.             t=tstatus=None
  849.             while srcpos:
  850.                 seek(srcpos)
  851.                 oid=read(8)
  852.                 if index[oid]==srcpos: return 0
  853.                 h=read(50) # serial, prev(oid), tloc, vlen, plen, pnv, pv
  854.                 tloc=h[16:24]
  855.                 if t != tloc:
  856.                     # We haven't checked this transaction before,
  857.                     # get it's status.
  858.                     t=tloc
  859.                     seek(U64(t)+16)
  860.                     tstatus=read(1)
  861.  
  862.                 if tstatus != 'u': return 1
  863.  
  864.                 spos=h[-8:]
  865.                 srcpos=U64(spos)
  866.  
  867.             return 1
  868.         finally: self._lock_release()
  869.  
  870.     def versions(self, max=None):
  871.         r=[]
  872.         a=r.append
  873.         keys=self._vindex.keys()
  874.         if max is not None: keys=keys[:max]
  875.         for version in keys:
  876.             if self.versionEmpty(version): continue
  877.             a(version)
  878.             if max and len(r) >= max: return r
  879.  
  880.         return r
  881.  
  882.     def history(self, oid, version=None, length=1, filter=None):
  883.         self._lock_acquire()
  884.         try:
  885.             r=[]
  886.             file=self._file
  887.             seek=file.seek
  888.             read=file.read
  889.             pos=self._index[oid]
  890.             wantver=version
  891.  
  892.             while 1:
  893.                 if len(r) >= length: return r
  894.                 seek(pos)
  895.                 h=read(42)
  896.                 doid,serial,prev,tloc,vlen,plen = unpack(">8s8s8s8sH8s", h)
  897.                 prev=U64(prev)
  898.  
  899.                 if vlen:
  900.                     nv = read(8) != z64
  901.                     file.seek(8,1) # Skip previous version record pointer
  902.                     version=read(vlen)
  903.                     if wantver is not None and version != wantver:
  904.                         if prev:
  905.                             pos=prev
  906.                             continue
  907.                         else:
  908.                             return r
  909.                 else:
  910.                     version=''
  911.                     wantver=None
  912.  
  913.                 seek(U64(tloc))
  914.                 h=read(23)
  915.                 tid, stl, status, ul, dl, el = unpack(">8s8scHHH",h)
  916.                 user_name=read(ul)
  917.                 description=read(dl)
  918.                 if el: d=loads(read(el))
  919.                 else: d={}
  920.  
  921.                 d['time']=TimeStamp(serial).timeTime()
  922.                 d['user_name']=user_name
  923.                 d['description']=description
  924.                 d['serial']=serial
  925.                 d['version']=version
  926.                 d['size']=U64(plen)
  927.  
  928.                 if filter is None or filter(d):
  929.                     r.append(d)
  930.  
  931.                 if prev: pos=prev
  932.                 else: return r
  933.         finally: self._lock_release()
  934.  
  935.     def _redundant_pack(self, file, pos):
  936.         file.seek(pos-8)
  937.         p=U64(file.read(8))
  938.         file.seek(pos-p+8)
  939.         return file.read(1) not in ' u'
  940.  
  941.     def pack(self, t, referencesf):
  942.         """Copy data from the current database file to a packed file
  943.     
  944.         Non-current records from transactions with time-stamp strings less
  945.         than packtss are ommitted. As are all undone records.
  946.     
  947.         Also, data back pointers that point before packtss are resolved and
  948.         the associated data are copied, since the old records are not copied.
  949.         """
  950.  
  951.         # Ugh, this seems long
  952.         
  953.         packing=1 # are we in the packing phase (or the copy phase)
  954.         locked=0
  955.         _lock_acquire=self._lock_acquire
  956.         _lock_release=self._lock_release
  957.         index, vindex, tindex, tvindex = self._newIndexes()
  958.         name=self.__name__
  959.         file=open(name, 'rb')
  960.         stop=`apply(TimeStamp, time.gmtime(t)[:5]+(t%60,))`
  961.         if stop==z64: raise FileStorageError, 'Invalid pack time'
  962.  
  963.         try:
  964.             ##################################################################
  965.             # Step 1, get index as of pack time that
  966.             # includes only referenced objects.
  967.  
  968.             # Record pack time so we don't undo while packing
  969.             _lock_acquire()
  970.             locked=1
  971.             if self._packt != z64:
  972.                 raise FileStorageError, 'Already packing'
  973.             self._packt=stop
  974.             _lock_release()
  975.             locked=0
  976.             
  977.             packpos, maxoid, ltid = read_index(
  978.                 file, name, index, vindex, tindex, stop,
  979.                 read_only=1,
  980.                 )
  981.  
  982.             if self._redundant_pack(file, packpos):
  983.                 raise FileStorageError, (
  984.                     'The database has already been packed to a later time\n'
  985.                     'or no changes have been made since the last pack')
  986.     
  987.             rootl=[z64]
  988.             pop=rootl.pop
  989.             pindex={}
  990.             referenced=pindex.has_key
  991.             _load=self._load
  992.             _loada=self._loada
  993.             v=None
  994.             while rootl:
  995.                 oid=pop()
  996.                 if referenced(oid): continue
  997.                 try:
  998.                     p, v, nv = _loada(oid, index, file)
  999.                     referencesf(p, rootl)
  1000.                     if nv:
  1001.                         p, serial = _load(oid, '', index, file)
  1002.                         referencesf(p, rootl)
  1003.     
  1004.                     pindex[oid]=index[oid]
  1005.                 except:
  1006.                     pindex[oid]=0
  1007.                     error('Bad reference to %s', `(oid,v)`)
  1008.     
  1009.             spackpos=p64(packpos)
  1010.     
  1011.             ##################################################################
  1012.             # Step 2, copy data and compute new index based on new positions.
  1013.             index, vindex, tindex, tvindex = self._newIndexes()
  1014.     
  1015.             ofile=open(name+'.pack', 'w+b')
  1016.     
  1017.             # Index for non-version data.  This is a temporary structure
  1018.             # to reduce I/O during packing
  1019.             nvindex={}
  1020.     
  1021.             # Cache a bunch of methods
  1022.             seek=file.seek
  1023.             read=file.read
  1024.             oseek=ofile.seek
  1025.             write=ofile.write
  1026.     
  1027.             tappend=tindex.append
  1028.             index_get=index.get
  1029.             vindex_get=vindex.get
  1030.             pindex_get=pindex.get
  1031.     
  1032.             # Initialize, 
  1033.             pv=z64
  1034.             offset=0L  # the amount of space freed by packing
  1035.             pos=opos=4L
  1036.             oseek(0)
  1037.             write(packed_version)
  1038.  
  1039.             # Copy the data in two stages.  In the packing stage,
  1040.             # we skip records that are non-current or that are for
  1041.             # unreferenced objects. We also skip undone transactions.
  1042.             #
  1043.             # After the packing stage, we copy everything but undone
  1044.             # transactions, however, we have to update various back pointers.
  1045.             # We have to have the storage lock in the second phase to keep
  1046.             # data from being changed while we're copying.
  1047.             pnv=None
  1048.             while 1:
  1049.  
  1050.                 # Check for end of packed records
  1051.                 if packing and pos >= packpos:
  1052.                     # OK, we're done with the old stuff, now we have
  1053.                     # to get the lock so we can copy the new stuff!
  1054.                     offset=pos-opos
  1055.                     if offset <= 0:
  1056.                         # we didn't free any space, there's no point in
  1057.                         # continuing
  1058.                         ofile.close()
  1059.                         file.close()
  1060.                         os.remove(name+'.pack')
  1061.                         return
  1062.                     
  1063.                     packing=0
  1064.                     _lock_acquire()
  1065.                     locked=1
  1066.                     self._packt=None # Prevent undo until we're done
  1067.  
  1068.                 # Read the transaction record
  1069.                 seek(pos)
  1070.                 h=read(23)
  1071.                 if len(h) < 23: break
  1072.                 tid, stl, status, ul, dl, el = unpack(">8s8scHHH",h)
  1073.                 if status=='c':
  1074.                     # Oops. we found a checkpoint flag.
  1075.                     break
  1076.                 tl=U64(stl)
  1077.                 tpos=pos
  1078.                 tend=tpos+tl
  1079.  
  1080.                 if status=='u':
  1081.                     if not packing:
  1082.                         # We rely below on a constant offset for unpacked
  1083.                         # records. This assumption holds only if we copy
  1084.                         # undone unpacked data. This is lame, but necessary
  1085.                         # for now to squash a bug.
  1086.                         write(h)
  1087.                         tl=tl+8
  1088.                         write(read(tl-23))
  1089.                         opos=opos+tl
  1090.                         
  1091.                     # Undone transaction, skip it
  1092.                     pos=tend+8
  1093.                     continue
  1094.  
  1095.                 otpos=opos # start pos of output trans
  1096.  
  1097.                 # write out the transaction record
  1098.                 status=packing and 'p' or ' '
  1099.                 write(h[:16]+status+h[17:])
  1100.                 thl=ul+dl+el
  1101.                 h=read(thl)
  1102.                 if len(h) != thl:
  1103.                     raise 'Pack Error', opos
  1104.                 write(h)
  1105.                 thl=23+thl
  1106.                 pos=tpos+thl
  1107.                 opos=otpos+thl
  1108.  
  1109.                 while pos < tend:
  1110.                     # Read the data records for this transaction
  1111.  
  1112.                     seek(pos)
  1113.                     h=read(42)
  1114.                     oid,serial,sprev,stloc,vlen,splen = unpack(
  1115.                         ">8s8s8s8sH8s", h)
  1116.                     plen=U64(splen)
  1117.                     dlen=42+(plen or 8)
  1118.  
  1119.                     if vlen:
  1120.                         dlen=dlen+(16+vlen)
  1121.                         if packing and pindex_get(oid,0) != pos:
  1122.                             # This is not the most current record, or
  1123.                             # the oid is no longer referenced so skip it.
  1124.                             pos=pos+dlen
  1125.                             continue
  1126.  
  1127.                         pnv=U64(read(8))
  1128.                         # skip position of previous version record
  1129.                         seek(8,1)
  1130.                         version=read(vlen)
  1131.                         pv=p64(vindex_get(version, 0))
  1132.                         vindex[version]=opos
  1133.                     else:
  1134.                         if packing:
  1135.                             ppos=pindex_get(oid, 0)
  1136.                             if ppos != pos:
  1137.                                 
  1138.                                 if not ppos:
  1139.                                     # This object is no longer referenced
  1140.                                     # so skip it.
  1141.                                     pos=pos+dlen
  1142.                                     continue
  1143.                                 
  1144.                                 # This is not the most current record
  1145.                                 # But maybe it's the most current committed
  1146.                                 # record.
  1147.                                 seek(ppos)
  1148.                                 ph=read(42)
  1149.                                 pdoid,ps,pp,pt,pvlen,pplen = unpack(
  1150.                                     ">8s8s8s8sH8s", ph)
  1151.                                 if not pvlen:
  1152.                                     # The most current record is committed, so
  1153.                                     # we can toss this one
  1154.                                     pos=pos+dlen
  1155.                                     continue
  1156.                                 pnv=read(8)
  1157.                                 pnv=_loadBackPOS(file, oid, pnv)
  1158.                                 if pnv > pos:
  1159.                                     # The current non version data is later,
  1160.                                     # so this isn't the current record
  1161.                                     pos=pos+dlen
  1162.                                     continue
  1163.  
  1164.                                 # Ok, we've gotten this far, so we have
  1165.                                 # the current record and we're ready to
  1166.                                 # read the pickle, but we're in the wrong
  1167.                                 # place, after wandering around to figure
  1168.                                 # out is we were current. Seek back
  1169.                                 # to pickle data:
  1170.                                 seek(pos+42)
  1171.  
  1172.                             nvindex[oid]=opos
  1173.  
  1174.                     tappend((oid,opos))
  1175.                     
  1176.                     opos=opos+dlen
  1177.                     pos=pos+dlen
  1178.  
  1179.                     if plen:
  1180.                         p=read(plen)
  1181.                     else:
  1182.                         p=read(8)
  1183.                         if packing:
  1184.                             # When packing we resolve back pointers!
  1185.                             p, serial = _loadBack(file, oid, p)
  1186.                             plen=len(p)
  1187.                             opos=opos+plen-8
  1188.                             splen=p64(plen)
  1189.                         else:
  1190.                             p=U64(p)
  1191.                             if p < packpos:
  1192.                                 # We have a backpointer to a
  1193.                                 # non-packed record. We have to be
  1194.                                 # careful.  If we were pointing to a
  1195.                                 # current record, then we should still
  1196.                                 # point at one, otherwise, we should
  1197.                                 # point at the last non-version record.
  1198.                                 ppos=pindex_get(oid,0)
  1199.                                 if ppos:
  1200.                                     if ppos==p:
  1201.                                         # we were pointing to the
  1202.                                         # current record
  1203.                                         p=index[oid]
  1204.                                     else:
  1205.                                         p=nvindex[oid]
  1206.                                 else:
  1207.                                     # Oops, this object was modified
  1208.                                     # in a version in which it was deleted.
  1209.                                     # Hee hee. It doesn't matter what we
  1210.                                     # use cause it's not reachable any more.
  1211.                                     p=0
  1212.                             else:
  1213.                                 # This points back to a non-packed record.
  1214.                                 # Just adjust for the offset
  1215.                                 p=p-offset
  1216.                             p=p64(p)
  1217.                             
  1218.                     sprev=p64(index_get(oid,0))
  1219.                     write(pack(">8s8s8s8sH8s",
  1220.                                oid,serial,sprev,p64(otpos),vlen,splen))
  1221.                     if vlen:
  1222.                         if not pnv:
  1223.                             write(z64)
  1224.                         else:
  1225.                             if pnv < packpos:
  1226.                                 # we need to point to the packed
  1227.                                 # non-version rec
  1228.                                 pnv=nvindex[oid]
  1229.                             else:
  1230.                                 # we just need to adjust the pointer
  1231.                                 # with the offset
  1232.                                 pnv=pnv-offset
  1233.                                 
  1234.                             write(p64(pnv))
  1235.                         write(pv)
  1236.                         write(version)
  1237.  
  1238.                     write(p)
  1239.  
  1240.                 # skip the (intentionally redundant) transaction length
  1241.                 pos=pos+8
  1242.  
  1243.                 if locked:
  1244.                     # temporarily release the lock to give other threads
  1245.                     # a chance to do some work!
  1246.                     _lock_release()
  1247.                     locked=0
  1248.  
  1249.                 for oid, p in tindex:
  1250.                     index[oid]=p # Record the position
  1251.  
  1252.                 del tindex[:]
  1253.  
  1254.                 # Now, maybe we need to hack or delete the transaction
  1255.                 otl=opos-otpos
  1256.                 if otl != tl:
  1257.                     # Oops, what came out is not what came in!
  1258.  
  1259.                     # Check for empty:
  1260.                     if otl==thl:
  1261.                         # Empty, slide back over the header:
  1262.                         opos=otpos
  1263.                         oseek(opos)
  1264.                     else:
  1265.                         # Not empty, but we need to adjust transaction length
  1266.                         # and update the status
  1267.                         oseek(otpos+8)
  1268.                         otl=p64(otl)
  1269.                         write(otl+status)
  1270.                         oseek(opos)
  1271.                         write(otl)
  1272.                         opos=opos+8
  1273.  
  1274.                 else:
  1275.                     write(p64(otl))
  1276.                     opos=opos+8
  1277.  
  1278.  
  1279.                 if not packing:
  1280.                     # We are in the copying phase.  Lets update the
  1281.                     # pack time and release the lock so others can write.
  1282.                     _lock_acquire()
  1283.                     locked=1
  1284.  
  1285.  
  1286.             # OK, we've copied everything. Now we need to wrap things
  1287.             # up.
  1288.  
  1289.             # Hack the files around.
  1290.             name=self.__name__
  1291.  
  1292.             ofile.flush()
  1293.             ofile.close()
  1294.             file.close()
  1295.             self._file.close()
  1296.             try:
  1297.                 if os.path.exists(name+'.old'):
  1298.                     os.remove(name+'.old')
  1299.                 os.rename(name, name+'.old')
  1300.             except:
  1301.                 # Waaa
  1302.                 self._file=open(name,'r+b')
  1303.                 raise
  1304.  
  1305.             # OK, we're beyond the point of no return
  1306.             os.rename(name+'.pack', name)
  1307.             self._file=open(name,'r+b')
  1308.             self._initIndex(index, vindex, tindex, tvindex)
  1309.             self._pos=opos
  1310.             self._save_index()
  1311.  
  1312.         finally:
  1313.  
  1314.             if locked: _lock_release()
  1315.  
  1316.             _lock_acquire()
  1317.             self._packt=z64
  1318.             _lock_release()
  1319.  
  1320. def shift_transactions_forward(index, vindex, tindex, file, pos, opos):
  1321.     """Copy transactions forward in the data file
  1322.  
  1323.     This might be done as part of a recovery effort
  1324.     """
  1325.  
  1326.     # Cache a bunch of methods
  1327.     seek=file.seek
  1328.     read=file.read
  1329.     write=file.write
  1330.  
  1331.     tappend=tindex.append
  1332.     index_get=index.get
  1333.     vindex_get=vindex.get
  1334.  
  1335.     # Initialize, 
  1336.     pv=z64
  1337.     p1=opos
  1338.     p2=pos
  1339.     offset=p2-p1
  1340.     packpos=opos
  1341.  
  1342.     # Copy the data in two stages.  In the packing stage,
  1343.     # we skip records that are non-current or that are for
  1344.     # unreferenced objects. We also skip undone transactions.
  1345.     #
  1346.     # After the packing stage, we copy everything but undone
  1347.     # transactions, however, we have to update various back pointers.
  1348.     # We have to have the storage lock in the second phase to keep
  1349.     # data from being changed while we're copying.
  1350.     pnv=None
  1351.     while 1:
  1352.  
  1353.         # Read the transaction record
  1354.         seek(pos)
  1355.         h=read(23)
  1356.         if len(h) < 23: break
  1357.         tid, stl, status, ul, dl, el = unpack(">8s8scHHH",h)
  1358.         if status=='c': break # Oops. we found a checkpoint flag.            
  1359.         tl=U64(stl)
  1360.         tpos=pos
  1361.         tend=tpos+tl
  1362.  
  1363.         otpos=opos # start pos of output trans
  1364.  
  1365.         thl=ul+dl+el
  1366.         h2=read(thl)
  1367.         if len(h2) != thl: raise 'Pack Error', opos
  1368.  
  1369.         # write out the transaction record
  1370.         seek(opos)
  1371.         write(h)
  1372.         write(h2)
  1373.  
  1374.         thl=23+thl
  1375.         pos=tpos+thl
  1376.         opos=otpos+thl
  1377.  
  1378.         while pos < tend:
  1379.             # Read the data records for this transaction
  1380.             seek(pos)
  1381.             h=read(42)
  1382.             oid,serial,sprev,stloc,vlen,splen = unpack(">8s8s8s8sH8s", h)
  1383.             plen=U64(splen)
  1384.             dlen=42+(plen or 8)
  1385.  
  1386.             if vlen:
  1387.                 dlen=dlen+(16+vlen)
  1388.                 pnv=U64(read(8))
  1389.                 # skip position of previous version record
  1390.                 seek(8,1)
  1391.                 version=read(vlen)
  1392.                 pv=p64(vindex_get(version, 0))
  1393.                 if status != 'u': vindex[version]=opos
  1394.  
  1395.             tappend((oid,opos))
  1396.  
  1397.             if plen: p=read(plen)
  1398.             else:
  1399.                 p=read(8)
  1400.                 p=U64(p)
  1401.                 if p >= p2: p=p-offset
  1402.                 elif p >= p1:
  1403.                     # Ick, we're in trouble. Let's bail
  1404.                     # to the index and hope for the best
  1405.                     p=index_get(oid,0)
  1406.                 p=p64(p)
  1407.  
  1408.             # WRITE
  1409.             seek(opos)
  1410.             sprev=p64(index_get(oid,0))
  1411.             write(pack(">8s8s8s8sH8s",
  1412.                        oid,serial,sprev,p64(otpos),vlen,splen))
  1413.             if vlen:
  1414.                 if not pnv: write(z64)
  1415.                 else:
  1416.                     if pnv >= p2: pnv=pnv-offset
  1417.                     elif pnv >= p1:
  1418.                         pnv=index_get(oid,0)
  1419.                         
  1420.                     write(p64(pnv))
  1421.                 write(pv)
  1422.                 write(version)
  1423.  
  1424.             write(p)
  1425.             
  1426.             opos=opos+dlen
  1427.             pos=pos+dlen
  1428.  
  1429.         # skip the (intentionally redundant) transaction length
  1430.         pos=pos+8
  1431.  
  1432.         if status != 'u': 
  1433.             for oid, p in tindex:
  1434.                 index[oid]=p # Record the position
  1435.  
  1436.         del tindex[:]
  1437.  
  1438.         write(stl)
  1439.         opos=opos+8
  1440.  
  1441.     return opos
  1442.  
  1443. def search_back(file, pos):
  1444.     seek=file.seek
  1445.     read=file.read
  1446.     seek(0,2)
  1447.     s=p=file.tell()
  1448.     while p > pos:
  1449.         seek(p-8)
  1450.         l=U64(read(8))
  1451.         if l <= 0: break
  1452.         p=p-l-8
  1453.  
  1454.     return p, s
  1455.  
  1456. def recover(file_name):
  1457.     file=open(file_name, 'r+b')
  1458.     index={}
  1459.     vindex={}
  1460.     tindex=[]
  1461.     
  1462.     pos, oid, tid = read_index(
  1463.         file, file_name, index, vindex, tindex, recover=1)
  1464.     if oid is not None:
  1465.         print "Nothing to recover"
  1466.         return
  1467.  
  1468.     opos=pos
  1469.     pos, sz = search_back(file, pos)
  1470.     if pos < sz:
  1471.         npos = shift_transactions_forward(
  1472.             index, vindex, tindex, file, pos, opos,
  1473.             )
  1474.  
  1475.     file.truncate(npos)
  1476.  
  1477.     print "Recovered file, lost %s, ended up with %s bytes" % (
  1478.         pos-opos, npos)
  1479.  
  1480.     
  1481.  
  1482. def read_index(file, name, index, vindex, tindex, stop='\377'*8,
  1483.                ltid=z64, start=4L, maxoid=z64, recover=0, read_only=0):
  1484.     
  1485.     read=file.read
  1486.     seek=file.seek
  1487.     seek(0,2)
  1488.     file_size=file.tell()
  1489.  
  1490.     if file_size:
  1491.         if file_size < start: raise FileStorageFormatError, file.name
  1492.         seek(0)
  1493.         if read(4) != packed_version: raise FileStorageFormatError, name
  1494.     else:
  1495.         if not read_only: file.write(packed_version)
  1496.         return 4L, maxoid, ltid
  1497.  
  1498.     index_get=index.get
  1499.     vndexpos=vindex.get
  1500.     tappend=tindex.append
  1501.  
  1502.     pos=start
  1503.     seek(start)
  1504.     unpack=struct.unpack
  1505.     tid='\0'*7+'\1'
  1506.  
  1507.     while 1:
  1508.         # Read the transaction record
  1509.         h=read(23)
  1510.         if not h: break
  1511.         if len(h) != 23:
  1512.             if not read_only:
  1513.                 warn('%s truncated at %s', name, pos)
  1514.                 seek(pos)
  1515.                 file.truncate()
  1516.             break
  1517.  
  1518.         tid, stl, status, ul, dl, el = unpack(">8s8scHHH",h)
  1519.         if el < 0: el=t32-el
  1520.  
  1521.         if tid <= ltid:
  1522.             warn("%s time-stamp reduction at %s", name, pos)
  1523.         ltid=tid
  1524.  
  1525.         tl=U64(stl)
  1526.  
  1527.         if pos+(tl+8) > file_size or status=='c':
  1528.             # Hm, the data were truncated or the checkpoint flag wasn't
  1529.             # cleared.  They may also be corrupted,
  1530.             # in which case, we don't want to totally lose the data.
  1531.             if not read_only:
  1532.                 warn("%s truncated, possibly due to damaged records at %s",
  1533.                      name, pos)
  1534.                 _truncate(file, name, pos)
  1535.             break
  1536.  
  1537.         if status not in ' up':
  1538.             warn('%s has invalid status, %s, at %s', name, status, pos)
  1539.  
  1540.         if ul > tl or dl > tl or el > tl or tl < (23+ul+dl+el):
  1541.             # We're in trouble. Find out if this is bad data in the
  1542.             # middle of the file, or just a turd that Win 9x dropped
  1543.             # at the end when the system crashed.
  1544.             # Skip to the end and read what should be the transaction length
  1545.             # of the last transaction.
  1546.             seek(-8, 2)
  1547.             rtl=U64(read(8))
  1548.             # Now check to see if the redundant transaction length is
  1549.             # reasonable:
  1550.             if file_size - rtl < pos or rtl < 23:
  1551.                 nearPanic('%s has invalid transaction header at %s', name, pos)
  1552.                 if not read_only:
  1553.                     warn("It appears that there is invalid data at the end of "
  1554.                          "the file, possibly due to a system crash.  %s "
  1555.                          "truncated to recover from bad data at end."
  1556.                          % name)
  1557.                     _truncate(file, name, pos)
  1558.                 break
  1559.             else:
  1560.                 if recover: return pos, None, None
  1561.                 panic('%s has invalid transaction header at %s', name, pos)
  1562.  
  1563.         if tid >= stop: break
  1564.  
  1565.         tpos=pos
  1566.         tend=tpos+tl
  1567.         
  1568.         if status=='u':
  1569.             # Undone transaction, skip it
  1570.             seek(tend)
  1571.             h=read(8)
  1572.             if h != stl:
  1573.                 if recover: return tpos, None, None
  1574.                 panic('%s has inconsistent transaction length at %s',
  1575.                       name, pos)
  1576.             pos=tend+8
  1577.             continue
  1578.  
  1579.         pos=tpos+(23+ul+dl+el)
  1580.         while pos < tend:
  1581.             # Read the data records for this transaction
  1582.  
  1583.             seek(pos)
  1584.             h=read(42)
  1585.             oid,serial,sprev,stloc,vlen,splen = unpack(">8s8s8s8sH8s", h)
  1586.             prev=U64(sprev)
  1587.             tloc=U64(stloc)
  1588.             plen=U64(splen)
  1589.             
  1590.             dlen=42+(plen or 8)
  1591.             tappend((oid,pos))
  1592.             
  1593.             if vlen:
  1594.                 dlen=dlen+(16+vlen)
  1595.                 seek(8,1)
  1596.                 pv=U64(read(8))
  1597.                 version=read(vlen)
  1598.                 # Jim says: "It's just not worth the bother."
  1599.                 #if vndexpos(version, 0) != pv:
  1600.                 #    panic("%s incorrect previous version pointer at %s",
  1601.                 #          name, pos)
  1602.                 vindex[version]=pos
  1603.  
  1604.             if pos+dlen > tend or tloc != tpos:
  1605.                 if recover: return tpos, None, None
  1606.                 panic("%s data record exceeds transaction record at %s",
  1607.                       name, pos)
  1608.                 
  1609.             if index_get(oid,0) != prev:
  1610.                 if prev:
  1611.                     if recover: return tpos, None, None
  1612.                     error("%s incorrect previous pointer at %s", name, pos)
  1613.                 else:
  1614.                     warn("%s incorrect previous pointer at %s", name, pos)
  1615.  
  1616.             pos=pos+dlen
  1617.  
  1618.         if pos != tend:
  1619.             if recover: return tpos, None, None
  1620.             panic("%s data records don't add up at %s",name,tpos)
  1621.  
  1622.         # Read the (intentionally redundant) transaction length
  1623.         seek(pos)
  1624.         h=read(8)
  1625.         if h != stl:
  1626.             if recover: return tpos, None, None
  1627.             panic("%s redundant transaction length check failed at %s",
  1628.                   name, pos)
  1629.         pos=pos+8
  1630.         
  1631.         for oid, p in tindex:
  1632.             maxoid=max(maxoid,oid)
  1633.             index[oid]=p # Record the position
  1634.  
  1635.         del tindex[:]
  1636.  
  1637.     return pos, maxoid, ltid
  1638.  
  1639.  
  1640. def _loadBack(file, oid, back):
  1641.     seek=file.seek
  1642.     read=file.read
  1643.     
  1644.     while 1:
  1645.         old=U64(back)
  1646.         if not old: raise KeyError, oid
  1647.         seek(old)
  1648.         h=read(42)
  1649.         doid,serial,prev,tloc,vlen,plen = unpack(">8s8s8s8sH8s", h)
  1650.  
  1651.         if vlen: seek(vlen+16,1)
  1652.         if plen != z64: return read(U64(plen)), serial
  1653.         back=read(8) # We got a back pointer!
  1654.  
  1655. def _loadBackPOS(file, oid, back):
  1656.     seek=file.seek
  1657.     read=file.read
  1658.     
  1659.     while 1:
  1660.         old=U64(back)
  1661.         if not old: raise KeyError, oid
  1662.         seek(old)
  1663.         h=read(42)
  1664.         doid,serial,prev,tloc,vlen,plen = unpack(">8s8s8s8sH8s", h)
  1665.         if vlen: seek(vlen+16,1)
  1666.         if plen != z64: return old
  1667.         back=read(8) # We got a back pointer!
  1668.  
  1669. def _truncate(file, name, pos):
  1670.     seek=file.seek
  1671.     seek(0,2)
  1672.     file_size=file.tell()
  1673.     try:
  1674.         i=0
  1675.         while 1:
  1676.             oname='%s.tr%s' % (name, i)
  1677.             if os.path.exists(oname):
  1678.                 i=i+1
  1679.             else:
  1680.                 warn("Writing truncated data from %s to %s", name, oname)
  1681.                 o=open(oname,'wb')
  1682.                 seek(pos)
  1683.                 cp(file, o, file_size-pos)
  1684.                 o.close()
  1685.                 break
  1686.     except:
  1687.         error("couldn\'t write truncated data for %s", name)
  1688.         raise POSException.StorageSystemError, (
  1689.             "Couldn't save truncated data")
  1690.             
  1691.     seek(pos)
  1692.     file.truncate()
  1693.